package com.book.oauth.config;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.book.oauth.server.role.entity.AuthRole;
import com.google.common.collect.Maps;
import org.apache.commons.compress.utils.Lists;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.store.redis.StandardStringSerializationStrategy;
import org.thymeleaf.util.StringUtils;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Description：
 * @Author： leo.xiong
 * @CreateDate： 2019/10/29 17:37
 * @Email： leo.xiong@suyun360.com
 * Version：1.0
 */
public class JacksonSerializationStrategy extends StandardStringSerializationStrategy {
    private static final Jackson2JsonRedisSerializer OBJECT_SERIALIZER = new Jackson2JsonRedisSerializer(Object.class);
    public static final String OAUTH_2_ACCESS_TOKEN = "OAuth2AccessToken";
    public static final String OAUTH_2_AUTHENTICATION = "OAuth2Authentication";
    public static final String OAUTH_2_REFRESH_TOKEN = "OAuth2RefreshToken";
    public static final String CLIENT_ID = "client_id";
    /**
     * 默认刷新时间 30 days.
     */
    private static final long REFRESH_TOKEN_VALIDITY_SECONDS = 60 * 60 * 24 * 30 * 1000L;
    /**
     * 默认存在时间12 hours.
     */
    private static final long ACCESS_TOKEN_VALIDITY_SECONDS = 60 * 60 * 12 * 1000L;

    private ClientDetailsService clientDetailsService;

    public void setClientDetailsService(ClientDetailsService clientDetailsService) {
        this.clientDetailsService = clientDetailsService;
    }

    @Override
    @SuppressWarnings("unchecked")
    protected <T> T deserializeInternal(byte[] bytes, Class<T> clazz) {
        if (clazz.getTypeName().endsWith(OAUTH_2_ACCESS_TOKEN)) {
            return (T) oAuth2AccessTokenDeserialize(bytes);
        } else if (clazz.getTypeName().endsWith(OAUTH_2_AUTHENTICATION)) {
            return (T) oAuth2AuthenticationDeserialize(bytes);
        } else if (clazz.getTypeName().endsWith(OAUTH_2_REFRESH_TOKEN)) {
            return (T) JSONObject.parseObject(JSONObject.toJSONString(OBJECT_SERIALIZER.deserialize(bytes)), DefaultExpiringOAuth2RefreshToken.class);
        }
        return JSONObject.parseObject(JSONObject.toJSONString(OBJECT_SERIALIZER.deserialize(bytes)), clazz);
    }

    @Override
    protected byte[] serializeInternal(Object object) {
        return OBJECT_SERIALIZER.serialize(object);
    }

    /**
     * 组装OAuth2AccessToken;
     *
     * @param bytes
     * @return
     */
    private OAuth2AccessToken oAuth2AccessTokenDeserialize(byte[] bytes) {
        JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(OBJECT_SERIALIZER.deserialize(bytes)));
        DefaultOAuth2AccessToken defaultOAuth2AccessToken = new DefaultOAuth2AccessToken("");
        Map<String, Object> additionalInformation = Maps.newLinkedHashMap();
        ClientDetails clientDetails = null;
        long systemTime = System.currentTimeMillis();
        for (String key : jsonObject.keySet()) {
            Object value = jsonObject.get(key);
            if (value == null) {
                continue;
            }
            if (OAuth2AccessToken.ACCESS_TOKEN.equals(key)) {
                defaultOAuth2AccessToken.setValue(value.toString());
            } else if (OAuth2AccessToken.EXPIRES_IN.equals(key)) {
                defaultOAuth2AccessToken.setExpiration(new Date(systemTime + (Long.valueOf(value.toString()) * 1000)));
            } else if (OAuth2AccessToken.TOKEN_TYPE.equals(key)) {
                defaultOAuth2AccessToken.setTokenType(value.toString());
            } else if (OAuth2AccessToken.SCOPE.equals(key)) {
                defaultOAuth2AccessToken.setScope(OAuth2Utils.parseParameterList(value.toString()));
            } else if (OAuth2AccessToken.REFRESH_TOKEN.equals(key)) {
                defaultOAuth2AccessToken.setRefreshToken(new DefaultOAuth2RefreshToken(value.toString()));
            } else {
                if (CLIENT_ID.equals(key) && clientDetails == null) {
                    clientDetails = clientDetailsService.loadClientByClientId(value.toString());
                }
                additionalInformation.put(key, value);
            }
        }
        defaultOAuth2AccessToken.setAdditionalInformation(additionalInformation);
        if (clientDetails == null) {
            return defaultOAuth2AccessToken;
        }
        defaultOAuth2AccessToken.setExpiration(new Date(systemTime + (clientDetails.getAccessTokenValiditySeconds() == null || clientDetails.getAccessTokenValiditySeconds() == 0 ? ACCESS_TOKEN_VALIDITY_SECONDS : clientDetails.getAccessTokenValiditySeconds() * 1000)));
        if (defaultOAuth2AccessToken.getRefreshToken() == null) {
            return defaultOAuth2AccessToken;
        }
        defaultOAuth2AccessToken.setRefreshToken(new DefaultExpiringOAuth2RefreshToken(defaultOAuth2AccessToken.getRefreshToken().getValue(), new Date(systemTime + (clientDetails.getRefreshTokenValiditySeconds() == null || clientDetails.getRefreshTokenValiditySeconds() == 0 ? REFRESH_TOKEN_VALIDITY_SECONDS : clientDetails.getRefreshTokenValiditySeconds() * 1000L))));
        return defaultOAuth2AccessToken;
    }

    /**
     * 组装OAuth2Authentication
     *
     * @param bytes
     * @return
     */
    private OAuth2Authentication oAuth2AuthenticationDeserialize(byte[] bytes) {
        JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(OBJECT_SERIALIZER.deserialize(bytes)));
        JSONObject oAuth2RequestJson = JSONObject.parseObject(jsonObject.get("oauth2Request").toString());
        OAuth2Request oauth2Request = JSONObject.parseObject(jsonObject.get("oauth2Request").toString(), OAuth2Request.class);
        JSONObject oauth2RequestJson = JSONObject.parseObject(jsonObject.get("oauth2Request").toString());
        Map<String, String> modifiable = JSONObject.parseObject(oAuth2RequestJson.get("requestParameters").toString(), HashMap.class);
        OAuth2Request oAuth2Request = new OAuth2Request(modifiable, oAuth2RequestJson.get("clientId").toString(), oauth2Request.getAuthorities(), Boolean.valueOf(oauth2RequestJson.get("approved").toString()), oauth2Request.getScope(),
                oauth2Request.getResourceIds(), oauth2Request.getRedirectUri(), oauth2Request.getResponseTypes(), oauth2Request.getExtensions());
        JSONObject usernamePasswordAuthenticationTokenJson = JSONObject.parseObject(jsonObject.get("userAuthentication").toString());
        String authorities = jsonObject.get("authorities").toString();
        List<AuthRole> authRoleList = Lists.newArrayList();
        if (!StringUtils.isEmpty(authorities)) {
            authRoleList = JSONArray.parseArray(authorities, AuthRole.class);
        }
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(usernamePasswordAuthenticationTokenJson.get("principal"), null, authRoleList);
        usernamePasswordAuthenticationToken.setDetails(usernamePasswordAuthenticationTokenJson.get("details"));
        OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, usernamePasswordAuthenticationToken);
        return oAuth2Authentication;
    }
}
